﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;

namespace Orbitone.CRM.Plugins.PhonenumberCleanup
{
    public class PhonenumberCleanup : IPlugin
    {
        private List<string> m_phoneNumberFields = new List<string>
                                                       {
                                                           "address1_telephone1",
                                                           "address1_telephone2",
                                                           "address1_telephone3",
                                                           "address1_fax",
                                                           "address2_telephone1",
                                                           "address2_telephone2",
                                                           "address2_telephone3",
                                                           "address2_fax",
                                                           "telephone1",
                                                           "telephone2",
                                                           "telephone3",
                                                           "fax",
                                                           "assistantphone",
                                                           "managerphone",
                                                           "mobilephone",
                                                           "pager",
                                                           "billto_telephone",
                                                           "shipto_telephone",
                                                           "billto_fax",
                                                           "shipto_Fax",
                                                       };

        private ICrmService m_service;

        public void Execute(IPluginExecutionContext context)
        {
            DynamicEntity entity = null;
            try
            {
                if (context.InputParameters.Properties.Contains("target") && context.InputParameters.Properties["target"] is DynamicEntity)
                {
                    entity = context.InputParameters.Properties["target"] as DynamicEntity;
                }
                if (entity == null)
                {
                    throw new NullReferenceException("target");
                }
                if (context.Stage == MessageProcessingStage.BeforeMainOperationOutsideTransaction)
                {
                    m_service = context.CreateCrmService(true);
                    convertPhoneNumbers(entity);
                }
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException("An error occurred in the PhoneNumberCleanup plug-in. " + ex, ex);
            }
        }

        private void convertPhoneNumbers(DynamicEntity entity)
        {
            var phoneproperties = entity.Properties.Where(prop => m_phoneNumberFields.Contains(prop.Name)).ToList();
            phoneproperties.ForEach(prop => entity.Properties[prop.Name] = cleanPhoneNumber(((StringProperty)prop).Value.ToString()));
        }

        private bool validatePhoneNumber(string sourceNumber)
        {
            const string pattern = @"[^0-9]+";
            var cleanNumber = Regex.Replace(sourceNumber.Trim(), pattern, "");
            if (cleanNumber == "")
            {
                return true;
            }
            if (cleanNumber.StartsWith("00"))
            {
                cleanNumber = cleanNumber.Substring(2);
            }
            
            return cleanNumber.Length >= 8
                && cleanNumber.Length <= 15;
        }
        private string cleanPhoneNumber(string sourceNumber)
        {
            if (string.IsNullOrEmpty(sourceNumber)) return "";
            const string pattern = @"[^0-9]+";
            if (!validatePhoneNumber(sourceNumber))
            {
                throw new ArgumentException("phonenumber is not valid");
            }

            var cleanNumber = Regex.Replace(sourceNumber.Trim(), pattern, "");

            if (string.IsNullOrEmpty(cleanNumber))
            {
                return "";
            }
            if (cleanNumber.StartsWith("00"))
            {
                cleanNumber = cleanNumber.Substring(2);
            }

            var criteria = new FilterExpression { FilterOperator = LogicalOperator.Or };
            criteria.Conditions.AddRange(new[]
                                             {
                                                 getEqualCondition("new_telephoneprefix", cleanNumber.Substring(0, 1)),
                                                 getEqualCondition("new_telephoneprefix", cleanNumber.Substring(0, 2)),
                                                 getEqualCondition("new_telephoneprefix", cleanNumber.Substring(0, 3)),
                                                 getEqualCondition("new_telephoneprefix", cleanNumber.Substring(0, 4)),
                                                 getEqualCondition("new_telephoneprefix", cleanNumber.Substring(0, 5)),
                                                 getEqualCondition("new_telephoneprefix", "32")
                                             });
            var countries = getMultipleEntities("new_country", criteria);

            var country = countries.Where(c => c.Properties["new_telephoneprefix"].ToString() != "32")
                                   .OrderByDescending(c => c.Properties["new_telephoneprefix"].ToString().Length)
                                   .FirstOrDefault()
                          ?? countries.Single(c => c.Properties["new_telephoneprefix"].ToString() == "32");

            cleanNumber = cleanNumber.Remove(0, country.Properties["new_telephoneprefix"].ToString().Length);

            criteria = new FilterExpression { FilterOperator = LogicalOperator.And };
            criteria.Conditions.Add(getEqualCondition("new_countryid", ((Key)country.Properties["new_countryid"]).Value));
            var areacodes = getMultipleEntities("new_areacode", criteria);

            if (cleanNumber.StartsWith("0"))
            {
                cleanNumber = cleanNumber.Substring(1);
            }

            var area = areacodes.Where(a => cleanNumber.StartsWith(a.Properties["new_areacode"].ToString()))
                                .OrderByDescending(a => a.Properties["new_areacode"].ToString().Length)
                                .Select(a => a.Properties["new_areacode"].ToString())
                                .FirstOrDefault();

            if (!string.IsNullOrEmpty(area) && cleanNumber.StartsWith(area)) cleanNumber = cleanNumber.Substring(area.Length);

            return string.IsNullOrEmpty(area)
                ? string.Format("+{0} {1}", country.Properties["new_telephoneprefix"], toDottedPhone(cleanNumber))
                : string.Format("+{0} ({1}) {2}", country.Properties["new_telephoneprefix"], area, toDottedPhone(cleanNumber));
        }

        private IEnumerable<DynamicEntity> getMultipleEntities(string entityName, FilterExpression criteria)
        {
            return ((RetrieveMultipleResponse)m_service.Execute(new RetrieveMultipleRequest
                                                                    {
                                                                        ReturnDynamicEntities = true,
                                                                        Query = new QueryExpression
                                                                                    {
                                                                                        ColumnSet = new AllColumns(),
                                                                                        Criteria = criteria,
                                                                                        EntityName = entityName,
                                                                                        Distinct = true
                                                                                    }
                                                                    })).BusinessEntityCollection.BusinessEntities.Cast<DynamicEntity>();
        }

        private ConditionExpression getEqualCondition(string fieldName, object value)
        {
            return new ConditionExpression
                       {
                           AttributeName = fieldName,
                           Operator = ConditionOperator.Equal,
                           Values = new[] { value }
                       };
        }

        private string toDottedPhone(string s)
        {
            var sb = new StringBuilder();
            var offset = s.Length;
            while (offset > 3)
            {
                sb.Insert(0, s.Substring(offset - 2, 2) + (offset == s.Length ? "" : "."));
                offset -= 2;
            }
            sb.Insert(0, s.Substring(0, offset) + ".");
            return sb.ToString();
        }
    }
}